/**
 * Copyright 2014 opencxa.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.cxa.rpc.cloud.server.components.authentication;

import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.cxa.misc.sqLiteUtils.SqLiteDatabase.DatabaseException;
import org.cxa.misc.sqLiteUtils.SqLiteDatabase;
import org.cxa.misc.ClassUtils;


/**
 * @author Christopher Armenio
 *
 */
public class SqLiteDatabaseManager extends DatabaseManager
{
	public static final String DEFAULT_DB = "opencxa.authdb";
	
	private static final String TABLE_NAME_USERS = "users";
	private static final String TABLE_NAME_GROUPS = "groups";
	private static final Map<String, Class<?>> TABLE_STRUCTURE;
	static
	{
		TABLE_STRUCTURE = new HashMap<String, Class<?>>();
		TABLE_STRUCTURE.put(TABLE_NAME_USERS, User.class);
		TABLE_STRUCTURE.put(TABLE_NAME_GROUPS, Group.class);
	}
	
	
	private SqLiteDatabase db;
	
	
	public void openDatabase(String dbFilePathIn) throws DatabaseException
	{
		this.logger.info(String.format("opening database file '%s'", dbFilePathIn));
		// setup our database structure		
		this.db = new SqLiteDatabase(new File(dbFilePathIn), TABLE_STRUCTURE, true);
	}
	
	
	public Boolean isOpen()
	{
		return (this.db != null) ? this.db.isOpen() : false;
	}
	
	
	@Override
	public List<User> getUserList() throws DatabaseManagerException
	{
		if( !this.isOpen() ) throw new DatabaseManagerException("database not open");
		
		List<User> retVal = super.getUserList();
		try
		{ 
			Object[] users_raw = this.db.listAllElementsInTable(TABLE_NAME_USERS);
			if( (users_raw != null) && (users_raw.length > 0) ) retVal.addAll(ClassUtils.objectArrayToList(users_raw, User.class));
		}
		catch( DatabaseException e ) { throw new DatabaseManagerException(e); }
		
		return retVal;
	}
	
	
	@Override
	public List<Group> getGroupList() throws DatabaseManagerException
	{
		if( !this.isOpen() ) throw new DatabaseManagerException("database not open");
		
		List<Group> retVal = super.getGroupList();
		try
		{ 
			Object[] groups_raw = this.db.listAllElementsInTable(TABLE_NAME_GROUPS);
			if( (groups_raw != null) && (groups_raw.length > 0) ) retVal.addAll(ClassUtils.objectArrayToList(groups_raw, Group.class));
		}
		catch( DatabaseException e ) { throw new DatabaseManagerException(e); }
		
		return retVal;
	}
	
	
	@Override
	public User getUser_byEmail(String emailIn) throws DatabaseManagerException
	{
		if( !this.isOpen() ) throw new DatabaseManagerException("database not open");
		
		User retVal = super.getUser_byEmail(emailIn);
		if( retVal != null ) return retVal;
		
		Map<String, String> colVals = new HashMap<String, String>();
		colVals.put(User.FIELD_NAME_EMAIL, emailIn);
		try
		{
			Object users[] = this.db.getElementsByColumnValues(TABLE_NAME_USERS, colVals);
			if( users.length > 1 ) this.logger.warn("multiple users found w/ email: '%s'", emailIn);
			else if( users.length == 1 ) retVal = (User)users[0];
		}
		catch( DatabaseException e ) { throw new DatabaseManagerException(e); }
		
		return retVal;
	}
	
	
	@Override
	public Group getGroup_byId(Integer idIn) throws DatabaseManagerException
	{
		if( !this.isOpen() ) throw new DatabaseManagerException("database not open");
		
		Group retVal = super.getGroup_byId(idIn);
		if( retVal != null ) return retVal;
		
		Map<String, Integer> colVals = new HashMap<String, Integer>();
		colVals.put(Group.FIELD_NAME_ID, idIn);
		try
		{
			Object groups[] = this.db.getElementsByColumnValues(TABLE_NAME_GROUPS, colVals);
			if( groups.length > 1 ) this.logger.warn("multiple groups found with id: '%d'", idIn);
			else if( groups.length == 1 ) retVal = (Group)groups[0];
		}
		catch( DatabaseException e ) { throw new DatabaseManagerException(e); }
		
		return retVal;
	}
	
	
	@Override
	public void addUser(User userIn) throws DatabaseManagerException
	{
		if( !this.isOpen() ) throw new DatabaseManagerException("database not open");
		
		// need to make sure the user doesn't have an ID
		if( userIn.getId() != null )
		{
			this.logger.warn(String.format("user to add '%s' already has an ID...aborting add", userIn.getEmail()));
			throw new DatabaseManagerException("user to add already has an ID");
		}
		
		// now, we need to make sure the email address doesn't already exist for this user
		if( this.getUser_byEmail(userIn.getEmail()) != null )
		{
			this.logger.warn(String.format("attempt to add duplicate user: '%s'", userIn.getEmail()));
			throw new DatabaseManagerException("email address already exists");
		}
		
		try{ this.db.insertRow(TABLE_NAME_USERS, userIn); }
		catch( DatabaseException e ) { throw new DatabaseManagerException(e); }
	}
	
	
	@Override
	public boolean updateUser(User userIn) throws DatabaseManagerException
	{
		if( !this.isOpen() ) throw new DatabaseManagerException("database not open");
		
		try{ return this.db.updateRow(TABLE_NAME_USERS, userIn); }
		catch( DatabaseException e ) { throw new DatabaseManagerException(e); }
	}
	
	
	@Override
	public boolean deleteUserByEmail(String emailIn) throws DatabaseManagerException
	{
		if( !this.isOpen() ) throw new DatabaseManagerException("database not open");
		
		Map<String, String> colVals = new HashMap<String, String>();
		colVals.put(User.FIELD_NAME_EMAIL, emailIn);
		try
		{
			return this.db.deleteRowsByColumnValues(TABLE_NAME_USERS, colVals);
		}
		catch( DatabaseException e ) { throw new DatabaseManagerException(e); }
	}
	
	
	@Override
	public void addGroup(Group groupIn) throws DatabaseManagerException
	{
		if( !this.isOpen() ) throw new DatabaseManagerException("database not open");
		
		// need to make sure the group doesn't have an ID
		if( groupIn.getId() != null )
		{
			this.logger.warn(String.format("group to add '%s' already has an ID...aborting add", groupIn.getName()));
			throw new DatabaseManagerException("group to add already has an ID");
		}
		
		try{ this.db.insertRow(TABLE_NAME_GROUPS, groupIn); }
		catch( DatabaseException e ) { throw new DatabaseManagerException(e); }
	}
	
	
	@Override
	public boolean updateGroup(Group groupIn) throws DatabaseManagerException
	{
		if( !this.isOpen() ) throw new DatabaseManagerException("database not open");
		
		try{ return this.db.updateRow(TABLE_NAME_USERS, groupIn); }
		catch( DatabaseException e ) { throw new DatabaseManagerException(e); }
	}
	
	
	@Override
	public boolean deleteGroupById(Integer idIn) throws DatabaseManagerException
	{
		if( !this.isOpen() ) throw new DatabaseManagerException("database not open");
		
		Map<String, Integer> colVals = new HashMap<String, Integer>();
		colVals.put(Group.FIELD_NAME_ID, idIn);
		try
		{
			boolean retVal = this.db.deleteRowsByColumnValues(TABLE_NAME_GROUPS, colVals);
			
			// now we need to iterate through our users and see if they belong to this group
			// if they do belong to this group, remove it from their list
			for( User currUser : this.getUserList() )
			{
				boolean userHasMods = false;
				List<Integer> currUserGroups = currUser.getGroups();
				Iterator<Integer> it = currUserGroups.iterator();
				while( it.hasNext() )
				{
					if( it.next() == idIn )
					{
						it.remove();
						userHasMods = true;
					}
				}
				
				if( userHasMods )
				{
					currUser.setGroups(currUserGroups.toArray(new Integer[currUserGroups.size()]));
					try{ this.updateUser(currUser); }
					catch(DatabaseManagerException e)
					{
						this.logger.warn(String.format("error removing group %d from user %@...continuing group remove operation", idIn, currUser.getEmail()));
					}
				}
			}
			
			return retVal;
		}
		catch( DatabaseException e ) { throw new DatabaseManagerException(e); }
	}
}
